package ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.imp;

import java.util.List;

import net.wimpi.modbus.ModbusIOException;
import net.wimpi.modbus.io.ModbusTransport;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.model.Device;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.DeviceChangeListener;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.DeviceController;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.DeviceManager;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.DeviceState;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.ImageChangeListener;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.ProcessImageDated;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.command.Command;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.command.CommandListener;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.devicecontroller.factories.ModbusTransportFactory;

public class DeviceManagerImp implements DeviceManager, CommandListener, ImageChangeListener {

	private Device device;
	
	private DeviceController controller;
	private ModbusTransportFactory transportfactory;
	
	private List<DeviceChangeListener> listeners;
	private Thread requestThread;
	private Thread commandThread;
	private Thread actualizationThread;
	private DeviceState state;
	
	//true if there's an actualization socket currently active
	private boolean actualizing  = false;
	
	private Runnable connectThread  = new ConnectAndPool();							
	private Runnable comamndThreadMethod = new CommandThreadMethod();
			

	@Override
	public boolean isConnected() {
		return getState() == DeviceState.CONNECTED;
	}

	@Override
	public boolean isTryingConection() {
		return getState() == DeviceState.TRYING_CONNECTION;
	}

	@Override
	public void connect() {
		if(device.getVisible()){
			synchronized (this) {
				if(getState() == DeviceState.CONNECTED || getState() == DeviceState.TRYING_CONNECTION )
					return;
				setState(DeviceState.TRYING_CONNECTION) ;
				requestThread = new Thread(connectThread);
				requestThread.start();
				commandThread = new Thread(comamndThreadMethod);
				commandThread.start();
				
			}
		}
		//Crear hilo no olvidar!!!!
	}
	@Override
	public void initAutomaticConnection(ModbusTransport incoming, AutomaticConnectionHandler handler) {
		try {
			boolean success = false;
			if(device.getAutoConection()){
				synchronized (this) {
					if(getState() == DeviceState.WAITING_CONNECTION ){
						success = true;
					}
					setState(DeviceState.CONNECTED);
					requestThread = Thread.currentThread();
					commandThread = new Thread(comamndThreadMethod);
					commandThread.start();
				}
				if(success)
					handler.sendConfirmation();
				else
					handler.sendFail();
				if(success)
					initRequestConnection(incoming);
			}
		} catch (ModbusIOException e) {
			disconnect();
		}
	}
	
	@Override
	public void initActualization(ModbusTransport incoming, ActualizationConnectionHandler handler) {
		try {
			
			synchronized(this){
				if(this.isConnected() && !this.isActualizing()){
					handler.sendConfirmation();
					setActualizing(true);
				}
				else {
					handler.sendFail();
				}
			}
		
			if(isActualizing()){
				actualizationThread = Thread.currentThread();
				initActualizationConnection(incoming);
			}
		
		}catch(ModbusIOException e){
			
		}
		finally{
			
		}
	}
	
	@Override
	public void disconnect() {
		//TODO: Ojo por la recursion infinita 
		if(commandThread != null) commandThread.interrupt();
		if(requestThread != null) requestThread.interrupt();
		if(actualizationThread != null) actualizationThread.interrupt();
		controller.setActualizationSocket(null);
		controller.setRequestSocket(null);
		controller.flushCommandQueue();
		actualizing = false;
		if(device.getVisible())
			setState(DeviceState.DISCONNECTED);
		else
			setState(DeviceState.WAITING_CONNECTION);

	}

	@Override
	public boolean isActualizing() {
		return actualizing;
	}
	
	@Override
	public void CommandEvent() {
		launchEvent();
	}
	
	@Override
	public void execute( Command cmd) {
		cmd.addCommandListener(this);
		controller.executeCommand(cmd);
	}

	@Override
	public void addDeviceChangeListener(DeviceChangeListener list) {
		listeners.add(list);
	}
	
	@Override
	public void removeDeviceChangeListener(DeviceChangeListener list) {
		listeners.remove(list);
	}
	private void initRequestConnection(
			ModbusTransport transport) {
		controller.setRequestSocket(transport);
		launchEvent();
        if(device.getSounding()){
        	controller.poolDevice();
        	disconnect();
        }
	}
	
	@Override
	public void change() {
		launchEvent();
	}
	
	private void initActualizationConnection(ModbusTransport incoming) {
		controller.setActualizationSocket(incoming);
		controller.actualizeDevice();
		controller.setActualizationSocket(null);
		setActualizing(false);
	}
	
	public void setActualizing(boolean b) {
		actualizing = b;
		launchEvent();
	}
	
	private void launchEvent() {
		for (DeviceChangeListener list : listeners) {
			list.changeState();
		}
	}

	/**
	 * @return the device
	 */
	public Device getDevice() {
		return device;
	}
	/**
	 * @param device the device to set
	 */
	public void setDevice(Device device) {
		this.device = device;
	}
	/**
	 * @return the controller
	 */
	public DeviceController getController() {
		return controller;
	}
	/**
	 * @param controller the controller to set
	 */
	public void setController(DeviceController controller) {
		this.controller = controller;
	}
	/**
	 * @return the transportfactory
	 */
	public ModbusTransportFactory getTransportfactory() {
		return transportfactory;
	}
	/**
	 * @param transportfactory the transportfactory to set
	 */
	public void setTransportfactory(ModbusTransportFactory transportfactory) {
		this.transportfactory = transportfactory;
	}
	/**
	 * @return the listeners
	 */
	public List<DeviceChangeListener> getListeners() {
		return listeners;
	}
	/**
	 * @param listeners the listeners to set
	 */
	public void setListeners(List<DeviceChangeListener> listeners) {
		this.listeners = listeners;
	}
	/**
	 * @return the state
	 */
	public DeviceState getState() {
		return state;
	}
	/**
	 * @param state the state to set
	 */
	public void setState(DeviceState state) {
		this.state = state;
		launchEvent();
	}

	public void initState() {
		if(device.getVisible())
			setState(DeviceState.DISCONNECTED);
		else
			setState(DeviceState.WAITING_CONNECTION);
	}
	
	
	private class ConnectAndPool implements Runnable {
		@Override
		public void run() {
			ModbusTransport transport =  transportfactory.connect(device.getIP(),device.getPort());
			if(transport != null){
				setState(DeviceState.CONNECTED);
				initRequestConnection(transport);
			}
			else{
				disconnect();
			}
			
		}
	};

	private class CommandThreadMethod implements Runnable  {
		@Override
		public void run() {
			controller.processCommands();
			disconnect();
		}
	}

	@Override
	public ProcessImageDated getProcessImage() {
		// TODO Auto-generated method stub
		return controller.getImage();
	};
}
